﻿using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Microsoft.Xna.Framework.Input;
using Phoenix.Xna.Framework.Input.Keyboards;
using Phoenix.Xna.Framework.Input.Mice;
using Phoenix.Xna.Framework.Serialization;
using MouseButtons = Phoenix.Xna.Framework.Input.Mice.Buttons;

namespace Phoenix.Xna.Framework.Input.Binding
{
    /// <summary>
    /// Manages the loading/saving of input binding data as well as binding delegates to IInputMaps.  This class is a singleton.
    /// </summary>
    public sealed class InputBindingManager
    {
        [SerializableIDictionary]
        private Dictionary<string, InputBindingCollection> _bindingCollection;  // The collections of InputBindings catagorized by ID.

        private class NestedSingleton
        {
            internal static readonly InputBindingManager Singleton = new InputBindingManager();

            static NestedSingleton() { }
        }

        /// <summary>
        /// Gets a singleton instance of the InputBindingManager.
        /// </summary>
        public static InputBindingManager Instance
        {
            get { return NestedSingleton.Singleton; }
        }

        private InputBindingManager()
        {
            _bindingCollection = new Dictionary<string, InputBindingCollection>();
        }

        /// <summary>
        /// Loads the specified xml file containing binding information.
        /// </summary>
        /// <param name="uri">The xml file to load.</param>
        public void Load(string uri)
        {
            XElement bindings = XElement.Load(uri);
            Serializer s = new Serializer();
            _bindingCollection = s.Deserialize<Dictionary<string, InputBindingCollection>>(bindings);
        }

        /// <summary>
        /// Binds the specified delegate to an IInputMap.
        /// </summary>
        /// <typeparam name="T">The type of IInputMap to bind to.</typeparam>
        /// <param name="id">The id of the collection containing the binding information.</param>
        /// <param name="action">The action associated with the binding.</param>
        /// <param name="inputEvent">The InputEvent to wire the delegate to.</param>
        /// <param name="handler">The delegate that will handle processing the IInputMap event.</param>
        public void BindInputMap<T>(string id, string action, InputEvent inputEvent, EventHandler<InputMapEventArgs<T>> handler)
        {
            IInputBinding binding = _bindingCollection[id][action];

            if (binding != null)
            {
                switch (binding.DeviceType)
                {
                    case DeviceType.Keyboard:
                        BindKeyMap(binding, inputEvent, handler as EventHandler<InputMapEventArgs<KeyboardKeyMap>>);
                        break;
                    case DeviceType.Mouse:
                        switch (binding.SubDeviceType)
                        {
                            case DeviceSubType.KeyboardKey:
                                // TODO : Throw exception.
                                break;
                            case DeviceSubType.MouseAxis:
                                BindMouseAxisMap(binding, inputEvent, handler as EventHandler<InputMapEventArgs<MouseAxisMap>>);
                                break;
                            case DeviceSubType.MouseButton:
                                BindMouseButtonMap(binding, inputEvent, handler as EventHandler<InputMapEventArgs<MouseButtonMap>>);
                                break;
                            case DeviceSubType.MouseScrollwheel:
                                BindMouseScrollWheelMap(binding, inputEvent, handler as EventHandler<InputMapEventArgs<MouseScrollWheelMap>>);
                                break;
                        }
                        break;
                    case DeviceType.GamePad:
                        // TODO : Implement game pads.
                        break;
                }
            }
        }

        private void BindKeyMap(IInputBinding binding, InputEvent inputEvent, EventHandler<InputMapEventArgs<KeyboardKeyMap>> handler)
        {
            if (binding is CompositeInputBinding<Keys>)
            {
                CompositeInputBinding<Keys> b = binding as CompositeInputBinding<Keys>;
                KeyboardKeyMap map = InputManager.Instance.MapKeys(b.Value);

                switch (inputEvent)
                {
                    case InputEvent.Pressed:
                        map.Pressed += handler;
                        break;
                    case InputEvent.Released:
                        map.Released += handler;
                        break;
                    case InputEvent.Held:
                        map.Held += handler;
                        break;
                    case InputEvent.ValueChanged:
                        // TODO : Throw exception.
                        break;
                }
            }
            else
            {
                // TODO : Throw exception.
            }
        }

        private void BindMouseButtonMap(IInputBinding binding, InputEvent inputEvent, EventHandler<InputMapEventArgs<MouseButtonMap>> handler)
        {
            if (binding is CompositeInputBinding<MouseButtons>)
            {
                CompositeInputBinding<MouseButtons> b = binding as CompositeInputBinding<MouseButtons>;
                MouseButtonMap map = InputManager.Instance.MapMouseButtons(b.Value);

                switch (inputEvent)
                {
                    case InputEvent.Pressed:
                        map.Pressed += handler;
                        break;
                    case InputEvent.Released:
                        map.Released += handler;
                        break;
                    case InputEvent.Held:
                        map.Held += handler;
                        break;
                    case InputEvent.ValueChanged:
                        // TODO : Throw exception.
                        break;
                }
            }
            else
            {
                // TODO : Throw exception.
            }
        }

        private void BindMouseAxisMap(IInputBinding binding, InputEvent inputEvent, EventHandler<InputMapEventArgs<MouseAxisMap>> handler)
        {
            if (binding is InputBinding)
            {
                MouseAxisMap map = InputManager.Instance.MapMouseAxis();

                switch (inputEvent)
                {
                    case InputEvent.Pressed:
                    case InputEvent.Released:
                    case InputEvent.Held:
                        // TODO : Throw exception.
                        break;
                    case InputEvent.ValueChanged:
                        map.ValueChanged += handler;
                        break;
                }
            }
        }

        private void BindMouseScrollWheelMap(IInputBinding binding, InputEvent inputEvent, EventHandler<InputMapEventArgs<MouseScrollWheelMap>> handler)
        {
            if (binding is InputBinding)
            {
                MouseScrollWheelMap map = InputManager.Instance.MapMouseScrollWheel();

                switch (inputEvent)
                {
                    case InputEvent.Pressed:
                    case InputEvent.Released:
                    case InputEvent.Held:
                        // TODO : Throw exception.
                        break;
                    case InputEvent.ValueChanged:
                        map.ValueChanged += handler;
                        break;
                }
            }
        }
    }
}
